/*
 * Flashlib - QB9 common Flash library
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.qb9.flashlib.input
{
	import com.qb9.flashlib.interfaces.IDisposable;
	
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	
	/**
	 * Provides an easy way to assign hotkeys (keyboard / mouse shortcuts).
	 * 
	 * @example Activate and add some hotkeys
	 * <listing version="3.0">
	 * // Inside a DisplayObject...
	 * var hotkey:Hotkey = new Hotkey(this);
	 * hotkey.add('Ctrl + a', function(e:KeyboardEvent) : void {...});
	 * hotkey.add('shift B', function(e:KeyboardEvent) : void {...}, 'mainMenu');
	 * hotkey.add('ctrl+shift+b', function(e:KeyboardEvent) : void {...}, 'gui');
	 * hotkey.add('ctrl', function(e:MouseEvent) : void {...}, null, MouseEvent.CLICK); 
	 * </listing>
	 */	
	public class Hotkey implements IDisposable
	{
		public static const DEFAULT_EVENT:String = KeyboardEvent.KEY_DOWN;
		
		public var on:Boolean = true;
		protected var registry:Object = {};
		
		protected var context:DisplayObjectContainer;
		protected function get stage() : Stage { return context.stage }
		
		
		/* Hotkey creation section */
		
		/**
		 * Creates a new Hotkey instance
		 * 
		 * @param context The context element
		 */ 
		
		public function Hotkey(context:DisplayObjectContainer)
		{
			this.context = context;
		}
		
		protected function needsStageBinding(event:String):Boolean
		{
			return event == KeyboardEvent.KEY_DOWN || event == KeyboardEvent.KEY_UP;
		}
		
		/**
		 * Register a new combination
		 * 
		 * <p><b>Each unique combination can only have one handler function.</b></p>
		 * <p>The combination is <b>case-insensitive</b> and can have 1 letter/number/Keyboard property (enter,space,up,etc).</p>
		 * <p>It can also contain modifiers (shift,ctrl,alt) separated by spaces
		 * and/or '+'. The modifiers must match exactly</p>
		 * <p>The handler will receive the corresponding KeyboardEvent/MouseEvent.</p>
		 * 
		 * @param combination A string specifying the desired shortcut.
		 * @param fn The handler function.
		 * @param event The related event (KEY_DOWN by default)
		 *
		 * @see flash.ui.Keyboard
		 * @see flash.events.KeyboardEvent 
		 */ 
		
		public function add(combination:String, fn:Function, event:String = null) : void
		{
			event = event || DEFAULT_EVENT;
			
			if (!registry[event])
			{
				if (needsStageBinding(event))
					stage.addEventListener(event, checkFromStage);
				else
					context.addEventListener(event, check);

				registry[event] = {};
			}

			for each(var combo:String in combination.split(/\s*,\s*/))
			{
				var shortcut:Shortcut = new Shortcut(combo);						
				registry[event][shortcut.uid] = fn;
			}			
		}
		
		/**
		 * Unregister a certain combination.
		 * 
		 * <p><b>Doesn't clean up bound events, you must call dispose() for that.</b></p>
		 * 
		 * @param combination A string specifying the desired shortcut.
		 * @param event The related event (KEY_DOWN by default)
		 *
		 * @see #add() 
		 * @see #dispose() 
		 */ 
		
		public function remove(combination:String, event:String = null) : void
		{
			event = event || DEFAULT_EVENT;

			if (registry[event])
			{
				for each(var combo:String in combination.split(/\s*,\s*/))
				{
					var shortcut:Shortcut = new Shortcut(combo);
					delete registry[event][shortcut.uid];
				}
			}
		}

		/**
		 * Disposes a Hotkey instance completely.
		 * 
		 * <b>You must call this method before removing the context from stage</b>
		 */ 
		
		public function dispose() : void
		{
			for (var event:String in registry)
			{
				if (needsStageBinding(event))
					stage.removeEventListener(event, checkFromStage);
				else
					context.removeEventListener(event, check);
			}
			
			registry = null;
			context = null;
		}

		protected function checkFromStage(e:Event) : void
		{
			var elem:DisplayObject = e.target as DisplayObject;
			if (on && elem && (elem === stage || context.contains(elem)))
				check(e);
		}
		
		protected function check(e:Event) : void
		{
			if (!on) return;
			
			var shortcut:Shortcut = Shortcut.fromEvent(e);
			
			var fn:Function = (registry[e.type] && registry[e.type][shortcut.uid]) as Function;
			if (fn === null)
				return;
			
			if (fn.length)
				fn(e);
			else
				fn();
		}

	}
}
